package com.v5ba.openapi;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yiren
 * @since 2022/7/15
 */
public class MyAdvice implements MethodBeforeAdvice, AfterReturningAdvice {

    private OpenAPIProperties openAPIProperties;
    public MyAdvice(OpenAPIProperties openAPIProperties) {
        this.openAPIProperties = openAPIProperties;
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("实现加密功能:"+returnValue);
        method.getReturnType().getDeclaredFields();
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 解密
        Map<String, Object> requestParam = decryptRequest(attr.getRequest());
        resetParam(method, args, requestParam);
    }

    /**
     * 请求参数初始化
     * @param method
     * @param args
     * @param requestParam
     */
    private void resetParam(Method method, Object[] args, Map<String, Object> requestParam)  {
        Parameter[] parameters = method.getParameters();
        Class<?>[] paramClass = method.getParameterTypes();
            for (int index = 0; index < paramClass.length; index++) {
                Class<?> clazz = paramClass[index];
                if (clazz == HttpServletRequest.class || clazz == HttpServletResponse.class) {
                    continue;
                }
                if (clazz == String.class || clazz == Byte.class || clazz == Short.class || clazz == Integer.class || clazz == Long.class || clazz == Float.class || clazz == Double.class) {
                    String attrName = parameters[index].getName();
                    args[index] = requestParam.get(attrName);
                } else if (clazz == Map.class) {
                    ((Map) args[index]).putAll(requestParam);
                } else {
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        try {
                            field.set(args[index], requestParam.get(field.getName()));
                        }catch (Exception e){
                            throw new RuntimeException("参数："+field.getName()+"设值失败");
                        }
                    }
                }
            }
    }

    /**
     * 将请求参数解密
     * @param request
     * @return
     */
    private Map<String, Object> decryptRequest(HttpServletRequest request){
        String sign = request.getParameter("sign");
        String encryptData = request.getParameter("encryptData");
        String encryptKey = request.getParameter("encryptKey");
        // 检查签名
        String signParamContent = encryptData + encryptKey;
        String signStr = SecureUtil.md5(signParamContent);
        if (signStr == null || !signStr.equals(sign)){
            throw new RuntimeException("验签失败");
        }
        // 解密key 获取AES密钥
        byte[] keyByte = null;
        try {
            RSA rsa = SecureUtil.rsa(openAPIProperties.getPrivateKey(), null);
            keyByte = rsa.decrypt(encryptKey, KeyType.PrivateKey);
        }catch (Exception e){
            throw new RuntimeException("参数错误，解密key失败");
        }
        // 解密encrypt
        try {
            AES aes = new AES(Mode.ECB, Padding.ISO10126Padding, keyByte);
            String _data = aes.decryptStr(encryptData);
            return JSONUtil.toBean(_data, Map.class);
        }catch (Exception e){
            throw new RuntimeException( "参数错误，解密encrypt失败");
        }
    }
}
